--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit a6b01321a36e8e6543ea567f479818d1fe127a2d
Parents : 5d3d511
Author : Sudo-Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-01-01T21:08:17-06:00
feat(voicemail): implement new voicemail notification callback and enhance call history retrieval with search and pagination support
Changes
5 files changed, 84 insertions(+), 5 deletions(-)
Diff
diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index a6b91072..6957e20b 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -372,6 +372,7 @@ class ReticulumMeshChat:
self.voicemail_manager.get_name_for_identity_hash = (
self.get_name_for_identity_hash
)
+ self.voicemail_manager.on_new_voicemail_callback = self.on_new_voicemail_received
# init Ringtone Manager
self.ringtone_manager = RingtoneManager(
@@ -1335,8 +1336,39 @@ class ReticulumMeshChat:
value = value.lower()
return value in {"1", "true", "yes", "on"}
+ def on_new_voicemail_received(self, remote_hash, remote_name, duration):
+ AsyncUtils.run_async(
+ self.websocket_broadcast(
+ json.dumps(
+ {
+ "type": "new_voicemail",
+ "remote_identity_hash": remote_hash,
+ "remote_identity_name": remote_name,
+ "duration": duration,
+ "timestamp": time.time(),
+ },
+ ),
+ ),
+ )
+
# handle receiving a new audio call
def on_incoming_telephone_call(self, caller_identity: RNS.Identity):
+ caller_hash = caller_identity.hash.hex()
+
+ # Check if caller is blocked
+ if self.is_destination_blocked(caller_hash):
+ print(f"Rejecting incoming call from blocked source: {caller_hash}")
+ if self.telephone_manager.telephone:
+ self.telephone_manager.telephone.hangup()
+ return
+
+ # Check for Do Not Disturb
+ if self.config.do_not_disturb_enabled.get():
+ print(f"Rejecting incoming call due to Do Not Disturb: {caller_hash}")
+ if self.telephone_manager.telephone:
+ self.telephone_manager.telephone.hangup()
+ return
+
# Trigger voicemail handling
self.voicemail_manager.handle_incoming_call(caller_identity)
@@ -1403,6 +1435,21 @@ class ReticulumMeshChat:
timestamp=time.time(),
)
+ # Trigger missed call notification if it was an incoming call that ended while ringing
+ if is_incoming and status_code == 4:
+ AsyncUtils.run_async(
+ self.websocket_broadcast(
+ json.dumps(
+ {
+ "type": "telephone_missed_call",
+ "remote_identity_hash": remote_identity_hash,
+ "remote_identity_name": remote_identity_name,
+ "timestamp": time.time(),
+ },
+ ),
+ ),
+ )
+
AsyncUtils.run_async(
self.websocket_broadcast(
json.dumps(
@@ -3139,7 +3186,13 @@ class ReticulumMeshChat:
@routes.get("/api/v1/telephone/history")
async def telephone_history(request):
limit = int(request.query.get("limit", 10))
- history = self.database.telephone.get_call_history(limit=limit)
+ offset = int(request.query.get("offset", 0))
+ search = request.query.get("search", None)
+ history = self.database.telephone.get_call_history(
+ search=search,
+ limit=limit,
+ offset=offset,
+ )
call_history = []
for row in history:
diff --git a/meshchatx/src/backend/config_manager.py b/meshchatx/src/backend/config_manager.py
index b4ef0094..67e5e813 100644
--- a/meshchatx/src/backend/config_manager.py
+++ b/meshchatx/src/backend/config_manager.py
@@ -135,6 +135,9 @@ class ConfigManager:
self.custom_ringtone_enabled = self.BoolConfig(self, "custom_ringtone_enabled", False)
self.ringtone_filename = self.StringConfig(self, "ringtone_filename", None)
+ # telephony config
+ self.do_not_disturb_enabled = self.BoolConfig(self, "do_not_disturb_enabled", False)
+
# map config
self.map_offline_enabled = self.BoolConfig(self, "map_offline_enabled", False)
self.map_offline_path = self.StringConfig(self, "map_offline_path", None)
diff --git a/meshchatx/src/backend/database/schema.py b/meshchatx/src/backend/database/schema.py
index 17fdbfc1..05852518 100644
--- a/meshchatx/src/backend/database/schema.py
+++ b/meshchatx/src/backend/database/schema.py
@@ -2,7 +2,7 @@ from .provider import DatabaseProvider
class DatabaseSchema:
- LATEST_VERSION = 18
+ LATEST_VERSION = 19
def __init__(self, provider: DatabaseProvider):
self.provider = provider
@@ -551,6 +551,11 @@ class DatabaseSchema:
"CREATE INDEX IF NOT EXISTS idx_contacts_remote_identity_hash ON contacts(remote_identity_hash)",
)
+ if current_version < 19:
+ self.provider.execute(
+ "CREATE INDEX IF NOT EXISTS idx_call_history_remote_name ON call_history(remote_identity_name)",
+ )
+
# Update version in config
self.provider.execute(
"""
diff --git a/meshchatx/src/backend/database/telephone.py b/meshchatx/src/backend/database/telephone.py
index eeb28cf3..2fe2f144 100644
--- a/meshchatx/src/backend/database/telephone.py
+++ b/meshchatx/src/backend/database/telephone.py
@@ -40,10 +40,19 @@ class TelephoneDAO:
),
)
- def get_call_history(self, limit=10):
+ def get_call_history(self, search=None, limit=10, offset=0):
+ if search:
+ return self.provider.fetchall(
+ """
+ SELECT * FROM call_history
+ WHERE remote_identity_name LIKE ? OR remote_identity_hash LIKE ?
+ ORDER BY timestamp DESC LIMIT ? OFFSET ?
+ """,
+ (f"%{search}%", f"%{search}%", limit, offset),
+ )
return self.provider.fetchall(
- "SELECT * FROM call_history ORDER BY timestamp DESC LIMIT ?",
- (limit,),
+ "SELECT * FROM call_history ORDER BY timestamp DESC LIMIT ? OFFSET ?",
+ (limit, offset),
)
def clear_call_history(self):
diff --git a/meshchatx/src/backend/voicemail_manager.py b/meshchatx/src/backend/voicemail_manager.py
index 5f8837e7..cfd128be 100644
--- a/meshchatx/src/backend/voicemail_manager.py
+++ b/meshchatx/src/backend/voicemail_manager.py
@@ -34,6 +34,8 @@ class VoicemailManager:
self.recording_remote_identity = None
self.recording_filename = None
+ self.on_new_voicemail_callback = None
+
# Paths to executables
self.espeak_path = self._find_espeak()
self.ffmpeg_path = self._find_ffmpeg()
@@ -377,6 +379,13 @@ class VoicemailManager:
f"Saved voicemail from {RNS.prettyhexrep(self.recording_remote_identity.hash)} ({duration}s)",
RNS.LOG_DEBUG,
)
+
+ if self.on_new_voicemail_callback:
+ self.on_new_voicemail_callback(
+ self.recording_remote_identity.hash.hex(),
+ remote_name,
+ duration,
+ )
else:
# Delete short/empty recording
filepath = os.path.join(self.recordings_dir, self.recording_filename)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────